Slide 1

Slide 1 text

c9s Callbackgen Generate callback methods without pain

Slide 2

Slide 2 text

Golang Taipei Gathering #2 2013

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

CallbackGen

Slide 6

Slide 6 text

CallbackGen = Callback Generator

Slide 7

Slide 7 text

Reflection vs Static Code Generation

Slide 8

Slide 8 text

Reflection handle dynamic type information in the runtime • dynamic type handling, especially interface types. • slower runtime, more CPU instructions. • more code for handling di ff erent types. • could cause runtime panic if types are not handled well.

Slide 9

Slide 9 text

Code generation handle types in the compile time • static code analysis + code generation. • compilation could fail if types are not handled well. • no runtime handling, faster code. • work like generic. • code completion is possible.

Slide 10

Slide 10 text

The Story started from… hand-written callbacks package main import ( "github.com/gorilla/websocket" ) type Client struct { connectCallbacks []func(c *websocket.Conn) disconnectCallbacks []func(c *websocket.Conn) } func (c *Client) OnConnect(cb func(c *websocket.Conn)) { c.connectCallbacks = append(c.connectCallbacks, cb) } func (c *Client) EmitConnect(conn *websocket.Conn) { for _, cb := range c.connectCallbacks { cb(conn) } }

Slide 11

Slide 11 text

⼿寫⼀次

Slide 12

Slide 12 text

⼿寫兩次

Slide 13

Slide 13 text

⼿寫三次

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

⼿寫 N 次

Slide 16

Slide 16 text

No content

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

No content

Slide 19

Slide 19 text

package main import ( "github.com/gorilla/websocket" ) type Client struct { connectCallbacks []func(c *websocket.Conn) disconnectCallbacks []func(c *websocket.Conn) } func (c *Client) OnConnect(cb func(c *websocket.Conn)) { c.connectCallbacks = append(c.connectCallbacks, cb) } func (c *Client) EmitConnect(conn *websocket.Conn) { for _, cb := range c.connectCallbacks { cb(conn) } } func (c *Client) OnDisconnect(cb func(c *websocket.Conn)) { c.disconnectCallbacks = append(c.disconnectCallbacks, cb) } func (c *Client) EmitDisconnect(conn *websocket.Conn) { for _, cb := range c.disconnectCallbacks { cb(conn) } }

Slide 20

Slide 20 text

package main import ( "github.com/gorilla/websocket" ) type Client struct { connectCallbacks []func(c *websocket.Conn) disconnectCallbacks []func(c *websocket.Conn) } func (c *Client) OnConnect(cb func(c *websocket.Conn)) { c.connectCallbacks = append(c.connectCallbacks, cb) } func (c *Client) EmitConnect(conn *websocket.Conn) { for _, cb := range c.connectCallbacks { cb(conn) } } func (c *Client) OnDisconnect(cb func(c *websocket.Conn)) { c.disconnectCallbacks = append(c.disconnectCallbacks, cb) } func (c *Client) EmitDisconnect(conn *websocket.Conn) { for _, cb := range c.disconnectCallbacks { cb(conn) } } package main import ( "github.com/gorilla/websocket" ) //go:generate callbackgen -type Client type Client struct { connectCallbacks []func(c *websocket.Conn) disconnectCallbacks []func(c *websocket.Conn) }

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

So, How Does It Work?

Slide 23

Slide 23 text

Go 1.5 transit to Go compiler from C compiler

Slide 24

Slide 24 text

go/types Go’s type checker

Slide 25

Slide 25 text

Parsing go/{parser, ast, scanner, token} Type Checker go/types vet lint eg gorename stringer mockgen mockery

Slide 26

Slide 26 text

a.go c.go b.go fmt Type Checker go/types compilation errors expressions type-checked packages imported packages

Slide 27

Slide 27 text

Step 1 parse package fi les package main func parsePackage(patterns []string, tags []string) { cfg := &packages.Config{ Mode: packages.NeedName | packages.NeedFiles | packages.NeedCompiledGoFiles | packages.NeedImports | packages.NeedTypes | packages.NeedTypesSizes | packages.NeedTypesInfo | packages.NeedFiles | packages.NeedSyntax | packages.NeedTypesInfo, Tests: false, BuildFlags: []string{fmt.Sprintf("-tags=%s", strings.Join(tags, " "))}, } pkgs, err := packages.Load(cfg, patterns...) if err != nil { log.Fatal(err) } }

Slide 28

Slide 28 text

Step 2 traverse the syntax tree for _, pkg := range pkgs { for i, file := range pkg.Syntax { ast.Inspect(file.file, func(node ast.Node) bool { switch decl := node.(type) { case *ast.ImportSpec: case *ast.FuncDecl: // .... } return true } } } USVFJUFSBUFJOUPUIFOPEF CMPDL TUBUFNFOU GVODUJPOʜFUD UIFOPEFUZQFUIBUZPVXBOUUPJOTQFDU

Slide 29

Slide 29 text

type ( // An ImportSpec node represents a single package import. ImportSpec struct { Doc *CommentGroup // associated documentation; or nil Name *Ident // local package name (including "."); or nil Path *BasicLit // import path Comment *CommentGroup // line comments; or nil EndPos token.Pos // end of spec (overrides Path.Pos if nonzero) } // A ValueSpec node represents a constant or variable declaration // (ConstSpec or VarSpec production). // ValueSpec struct { Doc *CommentGroup // associated documentation; or nil Names []*Ident // value names (len(Names) > 0) Type Expr // value type; or nil Values []Expr // initial values; or nil Comment *CommentGroup // line comments; or nil } ) import ( "golang.org/x/tools/go/packages" ) a := 1 + 2

Slide 30

Slide 30 text

Step 3 Resolve the AST node to Type Info switch decl := node.(type) { case *ast.FuncDecl: // get the first receiver node receiver := decl.Recv.List[0] // pass the receiver type token to the type info map receiverTypeAndValue, ok := pkg.TypesInfo.Types[receiver.Type] if !ok { return true } // inspect the REAL type switch v := recvTV.Type.(type) { case *types.Named: // type A int case *types.Pointer: // *int } } 5ZQF"OE7BMVFDPOUBJOTUIFUZQFJOGPSNBUJPO VTFUIFUZQFJOGPSNBUJPOUPDIFDL

Slide 31

Slide 31 text

Step 4 generate the code func renderCode() { var sliceCallbackTmpl = template.New("slice-callbacks").Funcs(funcMap) sliceCallbackTmpl = template.Must(sliceCallbackTmpl.Parse(` func ( {{- .RecvName }} *{{ .Field.StructName -}} ) On{{- .Field.EventName -}} (cb {{ .Field.CallbackTypeName .Qualifier -}} ) {{ .RecvName }}.{{ .Field.FieldName }} = append({{- .RecvName }}.{{ .Field.FieldName }}, cb) } func ( {{- .RecvName }} *{{ .Field.StructName -}} ) Emit{{- .Field.EventName -}} {{ .Field.CallbackParamsTuple | typeString }} for _, cb := range {{ .RecvName }}.{{ .Field.FieldName }} { cb({{ .Field.CallbackParamsVarNames | join ", " }}) } } `) sliceCallbackTmpl.Execute(&buf, TemplateArgs{ Field: field, RecvName: recvName, Qualifier: qf, GenerateRemoveMethod: *generateRemoveMethod, }) }

Slide 32

Slide 32 text

Step 5 reformat the code import ( "go/format" ) src, err := format.Source(buf.Bytes()) if err != nil { // Should never happen, but can arise when developing this code. // The user can compile the output to see the error. log.Printf("warning: internal error: invalid Go generated: %s", err) log.Printf("warning: compile the package to analyze the error") return buf.Bytes() } return src

Slide 33

Slide 33 text

Step 6 compile and test your code!

Slide 34

Slide 34 text

Step 7 copy n paste my code, write your own codegen and have fun

Slide 35

Slide 35 text

Your Filter Friends Filtering the AST nodes • FilterDecl(decl Decl, f Filter) bool • FilterFile(src *File, f Filter) bool • FilterPackage(pkg *Package, f Filter) bool • Inspect(node Node, f func(Node) bool)

Slide 36

Slide 36 text

No content

Slide 37

Slide 37 text

Codegen For Fun

Slide 38

Slide 38 text

https://github.com/MaiAmis/Careers