by ktr
Speaker Deck

Slide 1

Slide 1 text

fmt fmt 1

Slide 2

Slide 2 text

$ whoami $ whoami (id:|@)ktr_0731 サーバサイドエンジニ ア STEINS;GATE / 響け!ユーフォニア ム 2

Slide 3

Slide 3 text

3

Slide 4

Slide 4 text

ktr0731/evans ktr0731/evans more expressive gRPC client more expressive gRPC client 4

Slide 5

Slide 5 text

fmt? fmt? コードフォーマッタ ソースコードを 強制的 に整形してくれ る 5

Slide 6

Slide 6 text

gofmt YAPF Prettier scalafmt etc. 6

Slide 7

Slide 7 text

gofmt を読む gofmt を読む golang/go/src/cmd/gofmt 7

Slide 8

Slide 8 text

前提環境 前提環境 golang/go master (157d8cfbc13fbc4c849075e905b0001fb248b5e6) 8

Slide 9

Slide 9 text

cmd/gofmt/gofmt.go cmd/gofmt/gofmt.go gofmtMain() gofmtMain() for i := 0; i < flag.NArg(); i++ { path := flag.Arg(i) switch dir, err := os.Stat(path); { case err != nil: report(err) case dir.IsDir(): walkDir(path) // NOTE; call processFil default: if err := processFile(path, nil, os.St report(err) } } } 9

Slide 10

Slide 10 text

cmd/gofmt/gofmt.go cmd/gofmt/gofmt.go processFile() processFile() file, sourceAdj, indentAdj, err := parse(fileSet, filename, sr if err != nil { return err } 10

Slide 11

Slide 11 text

cmd/gofmt/internal.go cmd/gofmt/internal.go parse() parse() parser.ParseFile file, err = parser.ParseFile(fset, filename, psrc, parserMode) if err == nil { sourceAdj = func(src []byte, indent int) []byte { src = src[indent+len("package p\n"):] return bytes.TrimSpace(src) } 11

Slide 12

Slide 12 text

go/parser go/parser ParseFile() ParseFile() go/parser: 構文解析用パッケージ Go ソースコードから AST ( 抽象構文木) を生 成 12

Slide 13

Slide 13 text

cmd/gofmt/gofmt.go cmd/gofmt/gofmt.go processFile() processFile() ast.SortImports(fileSet, file) if *simplifyAST { simplify(file) } res, err := format(fileSet, file, sourceAdj, indentAdj, src, p if err != nil { return err } 13

Slide 14

Slide 14 text

cmd/gofmt/internal.go cmd/gofmt/internal.go format format var buf bytes.Buffer err := cfg.Fprint(&buf, fset, file) if err != nil { return nil, err } return buf.Bytes(), nil 14

Slide 15

Slide 15 text

go/printer/printer.go go/printer/printer.go Fprint() → Fprint() → fprint() fprint() AST ノードを整形・出力するパッケージ var p printer p.init(cfg, fset, nodeSizes) if err = p.printNode(node); err != nil { return } 15

Slide 16

Slide 16 text

go/printer/printer.go go/printer/printer.go printNode() printNode() switch n := node.(type) { case ast.Expr: p.expr(n) case ast.Stmt: if _, ok := n.(*ast.LabeledStmt); ok { p.indent = 1 } p.stmt(n, false) case ast.Decl: p.decl(n) case ast.Spec: p.spec(n, 1, false) case []ast.Stmt: for _, s := range n { if _, ok := s.(*ast.LabeledStmt); ok { 16

Slide 17

Slide 17 text

printNode printNode AST を再帰的に辿り、 各ノードの持つ内容を整 形・出力する 17

Slide 18

Slide 18 text

例: 二項演算子のスペース 例: 二項演算子のスペース fmt.Println("Hello, "+"World") fmt.Println("Hello, " + "World") 18

Slide 19

Slide 19 text

AST AST の構造 の構造 0 *ast.CallExpr { 1 . Fun: *ast.SelectorExpr { 2 . . X: *ast.Ident { 3 . . . NamePos: 1 4 . . . Name: "fmt" 5 . . . Obj: *ast.Object { 6 . . . . Kind: bad 7 . . . . Name: "" 8 . . . } 9 . . } 10 . . Sel: *ast.Ident { 11 . . . NamePos: 5 12 . . . Name: "Println" 13 . . } 14 . } 19

Slide 20

Slide 20 text

20

Slide 21

Slide 21 text

go/printer/printer.go go/printer/printer.go printNode() printNode() switch n := node.(type) { case ast.Expr: p.expr(n) func (p *printer) expr(x ast.Expr) { const depth = 1 p.expr1(x, token.LowestPrec, depth) } 21

Slide 22

Slide 22 text

go/printer/nodes.go go/printer/nodes.go expr1() expr1() switch x := expr.(type) { case *ast.BinaryExpr: if depth < 1 { p.internalError("depth < 1:", depth) depth = 1 } p.binaryExpr(x, prec1, cutoff(x, depth), depth) 22

Slide 23

Slide 23 text

go/printer/nodes.go go/printer/nodes.go binaryExpr() binaryExpr() ( 一部略) ( 一部略) // X 評価 p.expr1(x.X, prec, depth+diffPrec(x.X, prec)) if printBlank { // 追加 p.print(blank) } // "+" p.print(x.OpPos, x.Op) if printBlank { // 追加 p.print(blank) } // Y 評価 p.expr1(x.Y, prec+1, depth+1) 23

Slide 24

Slide 24 text

gofmt のフォーマットルール gofmt のフォーマットルール AST 生成までの過程で取り除かれるも の go/printer によって整形されるもの 24

Slide 25

Slide 25 text

AST AST 生成までの過程で取り除かれるもの 生成までの過程で取り除かれるもの 改行 スペー ス etc. 25

Slide 26

Slide 26 text

go/printer によって整形されるもの go/printer によって整形されるもの 二項演算子のスペース ( 前 述) 配列の最後の要素のカンマ etc. []string{"foo", "bar",} []string{"foo", "bar"} 26

Slide 27

Slide 27 text

似たようなのを実装してみる 似たようなのを実装してみる 27

Slide 28

Slide 28 text

ktr0731/markdownfmt ktr0731/markdownfmt 28

Slide 29

Slide 29 text

29

Slide 30

Slide 30 text

30

Slide 31

Slide 31 text

31

Slide 32

Slide 32 text

整形ルール 整形ルール 余計な改行・空白は取り除く テキストの最大長を 80 文字 に 32

Slide 33

Slide 33 text

整形ルール 整形ルール テキストの単語間のスペースは1つ に this is a text! this is a text! 33

Slide 34

Slide 34 text

整形ルール 整形ルール 水平線 (hr) の上下に空 行 horizontal --- line horizontal --- line 34

Slide 35

Slide 35 text

demo demo 35

Slide 36

Slide 36 text

これはデモ用のテキストです これはデモ用のテキストです this text is an example text for demonstration. current version of markdownfmt accepts 80 charactors as the max width. so, maybe this text will be splitted to some lines. 36

Slide 37

Slide 37 text

まとめ まとめ AST なんでもできてすごい 特に Go は取得できる情報が多い AST を作れれば、フォーマットはそこまで難しく ない 意外と gofmt は読みやすい 37

Slide 38

Slide 38 text

参考 参考 printer - The Go Programming Language parser - The Go Programming Language Go のための Go GitHub Flavored Markdown Spec 38