読者です 読者をやめる 読者になる 読者になる

今川館

都内勤務の地味OLです

Goの抽象構文木の利用例を読んだ

goにはコードの自動生成ツールが付属しているらしい。

Go と reflect と generate
http://qiita.com/naoina/items/7966f73f3a807b3d25d6

ここで「AST からコード生成する方法」という節の内容に興味を持った。

文字列テンプレートからコードを生成するのではなく、抽象構文木から生成する方法である。

IdentとかFuncDecとかいう概念がよくわからないので抽象構文木の使い方でぐぐったらt2yの書いた記事がヒットしてしまった。

Python の ast モジュール入門 (抽象構文木を辿る)
http://qiita.com/t2y/items/0964d01bf3db0233e3c1

めぼしい記載は無かったが、これを読んでいて、astに特定の決まった構成要素が定められているわけではないことは理解できた。
つまりGoのastのドキュメントを読まなければ自分の知りたいことはわからない、と。
そして、ひとまずGoのastのドキュメントに戻った。

type Ident
     An Ident node represents an identifier.
type Ident struct {
        NamePos token.Pos // identifier position
        Name    string    // identifier name
        Obj    *Object  // denoted object; or nil
}
type FuncDecl
     A FuncDecl node represents a function declaration.
type FuncDecl struct {
        Doc  *CommentGroup // associated documentation; or nil
        Recv *FieldList    // receiver (methods); or nil (functions)
        Name *Ident        // function/method name
        Type *FuncType    // function signature: parameters, results, and position of "func" keyword
        Body *BlockStmt    // function body; or nil (forward declaration)
}
type BasicLit
     A BasicLit node represents a literal of basic type.
type BasicLit struct {
        ValuePos token.Pos  // literal position
        Kind    token.Token // token.INT, token.FLOAT, token.IMAG, token.CHAR, or token.STRING
        Value    string      // literal string; e.g. 42, 0x7f, 3.14, 1e-9, 2.4i, 'a', '\x7f', "foo" or `\m\n\o`
}
type CompositeLit
     A CompositeLit node represents a composite literal.
type CompositeLit struct {
        Type  Expr      // literal type; or nil
        Lbrace token.Pos // position of "{"
        Elts  []Expr    // list of composite elements; or nil
        Rbrace token.Pos // position of "}"
}

問題は以下のコードを理解することだ。

        case *ast.Ident:
            if strings.Contains(aType.Name, "PlaceHolder") {
                aType.Name = strings.Replace(aType.Name, "PlaceHolder", structName, 1)
            }
        case *ast.FuncDecl:
            if aType.Recv == nil {
                break
            }
            switch aType.Name.Name {
            case "columns":
                aType.Body.List[0].(*ast.ReturnStmt).Results[0].(*ast.BasicLit).Value = strconv.Quote(strings.Join(columns, ", "))
            case "fields":
                recvName := aType.Recv.List[0].Names[0].Name
                clit := aType.Body.List[0].(*ast.ReturnStmt).Results[0].(*ast.CompositeLit)
                clit.Elts = clit.Elts[:0]
                for _, c := range columns {
                    clit.Elts = append(clit.Elts, &ast.UnaryExpr{
                        Op: token.AND,
                        X: &ast.SelectorExpr{
                            X:  ast.NewIdent(recvName),
                            Sel: ast.NewIdent(c),
                        },
                    })
                }
            }
        }

Identがメソッドのレシーバーや関数の引数に相当するらしい。
その識別子の名前が "PlaceHolder" を含めば、所定の名前に書き換える、という処理が最初のif節。

次が、FuncDeclは関数の定義を示すらしい。

その関数がレシーバーを持っていなければbreak。メソッドしか書き換えないから普通の関数が来たら処理を打ち切る。

そして、引数の名前が "columns" または "fields" ならばチョメチョメする。

"columns" が来たとき
最初の行をreturnステートメントとみなし、その文に含まれる先頭のリテラル値を書き換える。

"fields"が来たとき
returnステートメントからコンポジット型のリテラルを取り出して内容を書き換える。

だいたいこんな感じのことをやっているんだろう。

どうもGoの名前の略し方が慣れないのだが(そもそもPythonはあまり名前を省略しないので)、

  • Identはidentifierの略
  • FuncDeclはFunction declarationの略
  • BasicLitはBasic type literal(基本データ型のリテラル, intとかstring)の略
  • CompositeLitはComposite type literal(コンポジット型のリテラル, mapとかスライス)の略

ということらしい。

次の目標

次の目標は

https://github.com/azihsoyn/IDDD_go_sample

ここに書いてある

$ go build +build=mock

この "+build=mock" の意味を理解すること。
やっていることはモックと本物のデータソースを置き換えることらしい。