Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
GoのASTをいじくって
新しいツールを作る
わかめ まさひろ
わかめ まさひろ @vvakame
TypeScript
Masahiro Wakame
DefinitelyTyped
appengine
photo from golang.org/doc/gopher/
めんどいことはしたくない
誰だってそうする
俺だってそうする
encoding/json
play.golang.org/p/T9uO25D2xz
…


type Game struct {

ID int64 `json:"id"`

Title string `json:"title"`

Pric...
encoding/json
…


type Game struct {

ID int64 `json:"id"`

Title string `json:"title"`

Price int `json:"price"`

InDevel...
jwg 作った
//go:generate jwg -output model_json.go .

package sample



…


// +jwg

type Game struct {

ID int64

Title stri...
//go:generate jwg -output model_json.go .

package sample



…


// +jwg

type Game struct {

ID int64

Title string

Pric...
自動生成!
type GameJson struct {

ID int64 `json:"id,omitempty"`

Title string `json:"title,omitempty"`

Price int `json:"pric...
その他!
type GameJson
func (orig *GameJson) Convert() (*Game, error)
type GameJsonBuilder
func NewGameJsonBuilder() *GameJson...
Web API作成用
play.golang.org/p/5wYA62Njvn
func (b *GameJsonBuilder) AddSite() *GameJsonBuilder {

b.AddAll()

b.Remove(b.ID)...
この間公開しました!
生成コードは特に依存なし
みんなも作ろう!
主張
• コード生成 is 便利
• GoだとGenericsないしコード増えがち
• コンパイル時チェックの恩恵!
• 文字列で指定とか時代遅れだよね∼
• 元コード→データ化→加工→生成!
• まずはソースコードを解析しないと!
正規表現で頑張る
http://play.golang.org/p/fsOl7CcjgB
package main



import (

"fmt"

"regexp"

)



func main() {

code := `

typ...
copyright @shati_ko
ASTを活用する
• AST = Abstract Syntax Tree
• 本来はコンパイラ内部の中間表現
• ソースコードをデータとして使える!
• コード解析はライブラリに任せよう!
• 解析後のコード組立に専念できる!
copyright @shati_ko
AST? コード生成??
Game struct → AST
type Game struct {

ID int64

Title string

Price int

InDevelopment bool

ShippedAt time.Time

}
ast = ...
Game struct → AST
type Game struct {

ID int64

Title string

Price int

InDevelopment bool

ShippedAt time.Time

}
ast = ...
Game struct → AST
type Game struct {

ID int64

Title string

Price int

InDevelopment bool

ShippedAt time.Time

}
ast = ...
Game struct → AST
type Game struct {

ID int64

Title string

Price int

InDevelopment bool

ShippedAt time.Time

}
ast = ...
Game struct → AST
type Game struct {

ID int64

Title string

Price int

InDevelopment bool

ShippedAt time.Time

}
ast = ...
Game struct → AST
type Game struct {

ID int64

Title string

Price int

InDevelopment bool

ShippedAt time.Time

}
ast = ...
Game struct → AST
type Game struct {

ID int64

Title string

Price int

InDevelopment bool

ShippedAt time.Time

}
ast = ...
Game struct → AST
type Game struct {

ID int64

Title string

Price int

InDevelopment bool

ShippedAt time.Time

}
ast = ...
AST→コード生成
_人人人人人人人人人人人人_
> 文字列組み立て頑張る <
 ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄
まじか
まじだ
開発のコツと構造
ここでは、struct読み取り→
ラッパ生成の流れに絞って解説する
ツール開発の流れ
1. 処理対象(のstruct)を決める
2. コード生成結果を手書きする
•名前を機械的に考えてつけよう
3. 必要な俺形式のデータ構造を設計する
•ASTから取れるか?不足はないか?
•型情報取れなくて辛いパターンある
4...
Goコードの構造
// generated by jwg -output model_json.go .; DO NOT EDIT



package sample



import (

"encoding/json"

"time"

...
俺形式が必要な理由
// generated by jwg -output model_json.go .; DO NOT EDIT



package sample



import (

"encoding/json"

"time"
...
代表的な俺形式
• Source (生成結果ソース全体)
• Struct (生成するstruct1個分)
• Field (↑のfield1個分)
• Tag (↑に付属するtag情報)
jwgの場合
// BuildStruct represents source code of assembling..

type BuildSource struct {

g *genbase.Generator

pkg *genbas...
genbaseのご紹介
• 3つほどコード生成ツール作った
• 定形処理の存在に気がつく
• AST読み込み
• 指定されたorタグ付きstructの収集
• import句の管理
• コード組み立て・フォーマット
• その他便利関数とか
gi...
そして気合
func (st *BuildStruct) emit(g *genbase.Generator) error {

g.Printf("// for %sn", st.Name())



// generate FooJson ...
デカさ
• genbase
• ざっくり580行くらい
• jwg
• ざっくり850行くらい
• 生成後コード読んでから読めば理解る
• …んじゃないかな多分
Tips
• 埋め込みstructは敵
• 生成すべきコードがどんどん複雑に…
• fieldの型がstructだと絶望
• ASTだけでは型の詳細な情報がない
• 生成コードないとコンパイル通らん
• Printfの %[1]s 記法マジ便利
I ♥ Pull Request
• よりGoらしい書き方できるよ!
• より効率の良い実装があるよ!
• Template使えよ!
• text/template は気に入らなかった…
• なんかないですかね?
github.com/favc...
宣伝
We are hiring!
• 開発:テレビ朝日
• jwg, genbase 他 爆誕!
• http://www.favclip.com/
• appengine/go開発者絶賛募集中!
Goに対する感想
疑問
• ライブラリのリビジョン?
• jwg 非互換な変更していいのかしら
• embedしたstructのメソッド呼び奴
• 外側のstructがreceiverになってほし
• Generics欲しい気持ちが抑えられない
怒り💢
• stringのslice取ると[]byteなのやめて💢
• 1文字=1バイトマンが作るライブラリ
• 再帰的なパッケージ参照許して💢
• 1パッケージが際限なくでかくなる…
• err != nil 毎回やるのだるい💢
Upcoming SlideShare
Loading in …5
×

GoCon 2015 Summer GoのASTをいじくって新しいツールを作る

11,379 views

Published on

GoのASTをいじくって新しいツールを作る

Published in: Technology
  • Be the first to comment

GoCon 2015 Summer GoのASTをいじくって新しいツールを作る

  1. 1. GoのASTをいじくって 新しいツールを作る わかめ まさひろ
  2. 2. わかめ まさひろ @vvakame TypeScript Masahiro Wakame DefinitelyTyped appengine photo from golang.org/doc/gopher/
  3. 3. めんどいことはしたくない 誰だってそうする 俺だってそうする
  4. 4. encoding/json play.golang.org/p/T9uO25D2xz … 
 type Game struct {
 ID int64 `json:"id"`
 Title string `json:"title"`
 Price int `json:"price"`
 InDevelopment bool `json:"inDevelopment"`
 ShippedAt time.Time `json:"shippedAt"`
 }
 
 func main() {
 game := &Game{
 ID: 1,
 Title: "Splatoon",
 Price: 5700,
 InDevelopment: false,
 }
 b, _ := json.Marshal(game)
 fmt.Println(string(b))
 }
  5. 5. encoding/json … 
 type Game struct {
 ID int64 `json:"id"`
 Title string `json:"title"`
 Price int `json:"price"`
 InDevelopment bool `json:"inDevelopment"`
 ShippedAt time.Time `json:"shippedAt"`
 }
 
 func main() {
 game := &Game{
 ID: 1,
 Title: "Splatoon",
 Price: 5700,
 InDevelopment: false,
 }
 b, _ := json.Marshal(game)
 fmt.Println(string(b))
 } 手書き!? 正気か!?!? めんどい “ 閉じるの忘れる typoる
  6. 6. jwg 作った //go:generate jwg -output model_json.go .
 package sample
 
 … 
 // +jwg
 type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 }
 
 func main() {
 game := &Game{
 ID: 1, Title: "Splatoon", Price: 5700, InDevelopment: false,
 }
 jsonObj, _ := NewGameJsonBuilder().AddAll().Convert(game)
 b, _ := json.Marshal(jsonObj)
 fmt.Println(string(b))
 } jwg = Json Wrapper Generator
  7. 7. //go:generate jwg -output model_json.go .
 package sample
 
 … 
 // +jwg
 type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 }
 
 func main() {
 game := &Game{
 ID: 1, Title: "Splatoon", Price: 5700, InDevelopment: false,
 }
 jsonObj, _ := NewGameJsonBuilder().AddAll().Convert(game)
 b, _ := json.Marshal(jsonObj)
 fmt.Println(string(b))
 } go generate 使う! コメントにタグ書く(標準仕様などない! 生成したコード利用だ! jwg 作った
  8. 8. 自動生成! type GameJson struct {
 ID int64 `json:"id,omitempty"`
 Title string `json:"title,omitempty"`
 Price int `json:"price,omitempty"`
 InDevelopment bool `json:"inDevelopment,omitempty"`
 ShippedAt time.Time `json:"shippedAt,omitempty"`
 } 楽
  9. 9. その他! type GameJson func (orig *GameJson) Convert() (*Game, error) type GameJsonBuilder func NewGameJsonBuilder() *GameJsonBuilder func (b *GameJsonBuilder) Add(info *GamePropertyInfo) *GameJsonBuilder func (b *GameJsonBuilder) AddAll() *GameJsonBuilder func (b *GameJsonBuilder) Convert(orig *Game) (*GameJson, error) func (b *GameJsonBuilder) ConvertList(orig []*Game) (GameJsonList, error) func (b *GameJsonBuilder) Marshal(orig *Game) ([]byte, error) func (b *GameJsonBuilder) Remove(info *GamePropertyInfo) *GameJsonBuilder type GameJsonList func (jsonList GameJsonList) Convert() ([]*Game, error) type GamePropertyDecoder type GamePropertyEncoder type GamePropertyInfo *JsonBuilder *Property(De|En)coder *PropertyInfo
  10. 10. Web API作成用 play.golang.org/p/5wYA62Njvn func (b *GameJsonBuilder) AddSite() *GameJsonBuilder {
 b.AddAll()
 b.Remove(b.ID) // IDは内部情報なのでいらない
 b.Price.Encoder = func(src *Game, dest *GameJson) error {
 if !src.InDevelopment {
 dest.Price = src.Price // 開発中じゃない時だけ価格を出すよ!
 }
 return nil
 }
 
 return b
 }
 
 func main() {
 game := &Game{
 ID: 2, Title: "Secret of Yaba", Price: 9999, InDevelopment: true,
 }
 jsonObj, _ := NewGameJsonBuilder().AddSite().Convert(game)
 b, _ := json.Marshal(jsonObj)
 fmt.Println(string(b))
 } { "title":"Secret of Yaba”, “inDevelopment":true, “shippedAt":"0001-01-01T00:00:00Z" } 実行結果→
  11. 11. この間公開しました! 生成コードは特に依存なし
  12. 12. みんなも作ろう!
  13. 13. 主張 • コード生成 is 便利 • GoだとGenericsないしコード増えがち • コンパイル時チェックの恩恵! • 文字列で指定とか時代遅れだよね∼ • 元コード→データ化→加工→生成! • まずはソースコードを解析しないと!
  14. 14. 正規表現で頑張る http://play.golang.org/p/fsOl7CcjgB package main
 
 import (
 "fmt"
 "regexp"
 )
 
 func main() {
 code := `
 type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 }
 `
 
 re:=regexp.MustCompile(`s*types+([a-zA-Z]+)s+structs+{n(?:s*([a-zA-Z0-9]+)s+([a-zA-Z0-9.]+)s*n)*s*}`)
 result := re.FindAllStringSubmatch(code,-1)
 fmt.Printf("%#v", result)
 }
  15. 15. copyright @shati_ko
  16. 16. ASTを活用する • AST = Abstract Syntax Tree • 本来はコンパイラ内部の中間表現 • ソースコードをデータとして使える! • コード解析はライブラリに任せよう! • 解析後のコード組立に専念できる!
  17. 17. copyright @shati_ko
  18. 18. AST? コード生成??
  19. 19. Game struct → AST type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 } ast = go/ast package ast.GenDecl
  20. 20. Game struct → AST type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 } ast = go/ast package ast.GenDecl ast.TypeSpec type (
 A struct {
 Foo string
 }
 B struct {
 Bar string
 }
 ) こういう記法もある(怖い
  21. 21. Game struct → AST type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 } ast = go/ast package ast.GenDecl ast.TypeSpec ast.Ident
  22. 22. Game struct → AST type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 } ast = go/ast package ast.GenDecl ast.TypeSpec ast.Ident ast.StructType
  23. 23. Game struct → AST type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 } ast = go/ast package ast.GenDecl ast.TypeSpec ast.Ident ast.StructType ast.FieldList
  24. 24. Game struct → AST type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 } ast = go/ast package ast.GenDecl ast.TypeSpec ast.Ident ast.StructType ast.FieldList ast.Field type A struct {
 Foo, Bar string
 } こういう記法もある(怖い
  25. 25. Game struct → AST type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 } ast = go/ast package ast.GenDecl ast.TypeSpec ast.Ident ast.StructType ast.FieldList ast.Field ast.Ident
  26. 26. Game struct → AST type Game struct {
 ID int64
 Title string
 Price int
 InDevelopment bool
 ShippedAt time.Time
 } ast = go/ast package ast.GenDecl ast.TypeSpec ast.Ident ast.StructType ast.FieldList ast.Field ast.Ident ast.Ident
  27. 27. AST→コード生成 _人人人人人人人人人人人人_ > 文字列組み立て頑張る <  ̄Y^Y^Y^Y^Y^Y^Y^Y^Y^Y^Y ̄ まじか まじだ
  28. 28. 開発のコツと構造 ここでは、struct読み取り→ ラッパ生成の流れに絞って解説する
  29. 29. ツール開発の流れ 1. 処理対象(のstruct)を決める 2. コード生成結果を手書きする •名前を機械的に考えてつけよう 3. 必要な俺形式のデータ構造を設計する •ASTから取れるか?不足はないか? •型情報取れなくて辛いパターンある 4. 頑張ってAST取って変換して生成処理書く
  30. 30. Goコードの構造 // generated by jwg -output model_json.go .; DO NOT EDIT
 
 package sample
 
 import (
 "encoding/json"
 "time"
 )
 
 // for Game
 type GameJson struct {
 ID int64 `json:"id,omitempty"`
 Title string `json:"title,omitempty"`
 Price int `json:"price,omitempty"`
 InDevelopment bool `json:"inDevelopment,omitempty"`
 ShippedAt time.Time `json:"shippedAt,omitempty"`
 }
 PackageClause ImportDecl TopLevelDecl
  31. 31. 俺形式が必要な理由 // generated by jwg -output model_json.go .; DO NOT EDIT
 
 package sample
 
 import (
 "encoding/json"
 "time"
 )
 
 // for Game
 type GameJson struct {
 ID int64 `json:"id,omitempty"`
 Title string `json:"title,omitempty"`
 Price int `json:"price,omitempty"`
 InDevelopment bool `json:"inDevelopment,omitempty"`
 ShippedAt time.Time `json:"shippedAt,omitempty"`
 }
 正しいPackageClauseの生成には、 TopLevelDecl生成結果の把握が必要! etc..
  32. 32. 代表的な俺形式 • Source (生成結果ソース全体) • Struct (生成するstruct1個分) • Field (↑のfield1個分) • Tag (↑に付属するtag情報)
  33. 33. jwgの場合 // BuildStruct represents source code of assembling..
 type BuildSource struct {
 g *genbase.Generator
 pkg *genbase.PackageInfo
 typeInfos genbase.TypeInfos
 
 Structs []*BuildStruct
 }
 
 // BuildStruct represents struct of assembling..
 type BuildStruct struct {
 parent *BuildSource
 typeInfo *genbase.TypeInfo
 
 Fields []*BuildField
 }
 
 // BuildField represents field of BuildStruct.
 type BuildField struct {
 parent *BuildStruct
 fieldInfo *genbase.FieldInfo
 
 Name string
 Embed bool
 Tag *BuildTag
 }
 
 // BuildTag represents tag of BuildField.
 type BuildTag struct {
 field *BuildField
 
 Name string
 Ignore bool // e.g. Secret string `json:"-"`
 DoNotEmit bool // e.g. Field int `json:",omitempty"`
 String bool // e.g. Int64String int64 `json:",string"`
 }
  34. 34. genbaseのご紹介 • 3つほどコード生成ツール作った • 定形処理の存在に気がつく • AST読み込み • 指定されたorタグ付きstructの収集 • import句の管理 • コード組み立て・フォーマット • その他便利関数とか github.com/favclip/genbase 参考:typewriter
  35. 35. そして気合 func (st *BuildStruct) emit(g *genbase.Generator) error {
 g.Printf("// for %sn", st.Name())
 
 // generate FooJson struct from Foo struct
 g.Printf("type %sJson struct {n", st.Name())
 for _, field := range st.Fields {
 if field.Tag.Ignore {
 continue
 }
 postfix := ""
 if field.WithJWG() {
 postfix = "Json"
 }
 tagString := field.Tag.TagString()
 if tagString != "" {
 tagString = fmt.Sprintf("`%s`", tagString)
 }
 if field.Embed {
 g.Printf("%s%s %sn", field.fieldInfo.TypeName(), postfix, tagString)
 } else {
 g.Printf("%s %s%s %sn", field.Name, field.fieldInfo.TypeName(), postfix, tagString)
 }
 }
 g.Printf("}nn")
 
 g.Printf("type %[1]sJsonList []*%[1]sJsonnn", st.Name())
 
 // generate property builder
 g.Printf("type %[1]sPropertyEncoder func(src *%[1]s, dest *%[1]sJson) errornn", st.Name())
 g.Printf("type %[1]sPropertyDecoder func(src *%[1]sJson, dest *%[1]s) errornn", st.Name())
 
 // generate property info
 g.Printf(`
 type %[1]sPropertyInfo struct {
 name string
 Encoder %[1]sPropertyEncoder
 Decoder %[1]sPropertyDecoder
 }
 
 `, st.Name())
 
 // generate json builder
 ↓ざっくり500行続く
  36. 36. デカさ • genbase • ざっくり580行くらい • jwg • ざっくり850行くらい • 生成後コード読んでから読めば理解る • …んじゃないかな多分
  37. 37. Tips • 埋め込みstructは敵 • 生成すべきコードがどんどん複雑に… • fieldの型がstructだと絶望 • ASTだけでは型の詳細な情報がない • 生成コードないとコンパイル通らん • Printfの %[1]s 記法マジ便利
  38. 38. I ♥ Pull Request • よりGoらしい書き方できるよ! • より効率の良い実装があるよ! • Template使えよ! • text/template は気に入らなかった… • なんかないですかね? github.com/favclip
  39. 39. 宣伝
  40. 40. We are hiring! • 開発:テレビ朝日 • jwg, genbase 他 爆誕! • http://www.favclip.com/ • appengine/go開発者絶賛募集中!
  41. 41. Goに対する感想
  42. 42. 疑問 • ライブラリのリビジョン? • jwg 非互換な変更していいのかしら • embedしたstructのメソッド呼び奴 • 外側のstructがreceiverになってほし • Generics欲しい気持ちが抑えられない
  43. 43. 怒り💢 • stringのslice取ると[]byteなのやめて💢 • 1文字=1バイトマンが作るライブラリ • 再帰的なパッケージ参照許して💢 • 1パッケージが際限なくでかくなる… • err != nil 毎回やるのだるい💢

×