Go静的解析ハンズオン
The Go gopher was designed by Renee French.
The gopher stickers was made by Takuya Ueda.
Licensed under the Creative Commons 3.0
Attributions license.
2017/06/25(日)
第6回Golang勉強会
in Okinawa
1
自己紹介
メルカリ/ソウゾウ
上田拓也
twitter: @tenntenn
■ コミュニティ活動
Google Cloud Platform User Group (GCPUG) Tokyo
Goビギナーズ
golang.tokyo
Go Conference
■ 業務
GAE/Goでメルカリカウルを作ってます
GoやGCPコミュニティを盛り上げる仕事
Gopherを描く仕事(LINEスタンプ)
2
2017年7月1日(土)
BigQueryのハンズオン開催!
https://okipug.connpass.com/event/56374/
3
アジェンダ
● Goにおける静的解析の方法(復習)
● ハンズオン
○ Hello, Worldをパースしてみよう
○ 手でHello, Worldを書いてみよう
○ gopherを探せ
○ 型情報を取得みよう
4
Goにおける静的解析の方法
(ハッカーズチャンプルーの復習)
5
goパッケージ
■ 標準パッケージとして静的解析の機能を提供
go/ast 抽象構文木(AST)を提供
go/build パッケージに関する情報を集める
go/constant 定数に関する型を提供
go/doc ドキュメントをASTから取り出す
go/format コードフォーマッタの機能を提供
go/importer コンパイラに適したImporterを提供
go/parser 構文解析の機能を提供
go/printer ASTの表示機能を提供
go/scanner 字句解析の機能を提供
go/token トークンに関する型を提供
go/types 型チェックに関する機能を提供
6
静的解析の流れ
7
ソースコード
トークン
抽象構文木(AST)
型情報
構文解析
字句解析
型チェック
go/scanner
go/token
go/parser
go/ast
go/types
go/constant
字句解析 - go/scanner,go/token
■ 文字列をトークンにしていく
● 空白などを取り除き、意味のある単位=トー
クンにしていく作業
8
IDENT ADD INT
トークン
ソースコード v + 1
v + 1
構文解析 - go/parser,go/ast
■ トークンを抽象構文木(AST)にしていく
● プログラムの構造を持たせる
9
IDENT ADD INT
ソースコード
+
v 1
BinaryExpr
Ident BasicLit
トークン
抽象構文木(AST)
n := 100 + 200
m := n + 300
型チェック - go/types,go/constant
■ 抽象構文木から型に関する情報を取得する
● 識別子の解決
● 型の推論
● 定数の評価
10
定数の評価
=300
型の推論
-> int
識別子の解決
抽象構文木(AST)の取得
■ go/parserパッケージの関数を使う
● ParseExpr,ParseExprFrom
○ 式をパースする
○ ParseExprはParseExprFromを簡易版
● ParseFile
○ ファイル単位でパースする
● ParseDir
○ ディレクトリ単位でパースする
○ 中でParseFileを呼んでいる
11
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Hello, Worldの抽象構文木の構成
12
Goの抽象構文木(AST)を手入力してHello, Worldを作る
http://qiita.com/tenntenn/items/0cbc6f1f00dc579fcd8c
Playgroundで動かす
*ast.File
[]ast.Decl
*ast.GenDecl *ast.FuncDecl
式の抽象構文木を取得する
■ 式単位を構文解析する
■ ParseExprFromでも書ける
13
expr, err := parser.ParseExpr(`v + 1`)
if err != nil {
/* エラー処理 */
}
/* exprを解析する処理 */
fset := token.NewFileSet() // ファイル情報
src := []byte(`v + 1`)
f := "" // ファイル名(式なので不要)
m := 0 // モード(式なので不要)
expr, err := parser.ParseExprFrom(fset, f, s, m)
const src = `
package main
var v = 100
func main() {
fmt.Println(v+1)
}`
fs := token.NewFileSet()
f, err := parser.ParseFile(fs, "my.go", src, 0)
if err != nil {
/* エラー処理 */
}
/* f を解析する処理 */
ソースから抽象構文木を取得する
■ Goのソースコードを構文解析する
14
引数はparse.ExprFromと
同じ構成
srcがnilだとファイル名
でファイルを開く
解析するファイルの中身
token.FileSetとは?
■ ファイル中の位置情報を記録する為の型
● 位置情報は数値で表される
● 複数のファイル間で一意の値
● 各ファイルのoffsetが記録されている
● パースする際に記録されていく
15
token.FileSetは出力引数として
Parse系の関数に渡す
*ast.BinaryExpr
*ast.Ident
*ast.BasicLit
v + 1
n, _ := parser.ParseExpr(`v + 1`)
ast.Inspect(n, func(n ast.Node) bool {
if n != nil { fmt.Printf("%Tn", n) }
return true
})
printer.Fprint(os.Stdout, token.NewFileSet(), n)
抽象構文木をトラバースする
■ ast.Inspectを使う
16
+
v 1
構文解析
抽象構文木(AST)を探索
抽象構文木(AST)を出力
BinaryExpr
Ident BasicLit
Playgroundで動かす
ast.Walkというのもある
func traverse(n ast.Node) {
switch n := n.(type) {
case *ast.Indent:
fmt.Println(n.Name)
case *ast.BinaryExpr:
traverse(n.X)
traverse(n.Y)
case *ast.UnaryExpr:
traverse(n.X)
}
}
抽象構文木をトラバースする
■ 再帰を使ってトラバースする
17
識別子の場合は名前を出力
二項演算式の場合は
各項を探索
単項演算式の場合は
項を探索
型でswitchする
Playgroundで動かす
ast.Nodeはインタフェース
参考資料
■ goパッケージで簡単に静的解析して世界を広げよう
● コードジェネレータ
○ ASTを取得する方法を調べる
○ 抽象構文木(AST)をトラバースする
○ 抽象構文木(AST)をいじってフォーマットをかける
○ Goの抽象構文木(AST)を手入力してHello, Worldを作る
○ go-app-builderのソースコードを読む
● リファクタリングツール
○ gorenameをライブラリとして使う
○ Goのスコープについて考えてみよう
○ go/typesパッケージを使い変数名をリネームしてみる
● 処理系
○ 簡単な式の評価機を作ってみる
○ 【実践goパッケージ】文字列から複素数型の値をパースする
○ もっと楽して式の評価器を作る
18
ハンズオン
19
Hello, Worldをパースしてみよう
20
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
fs := token.NewFileSet()
f, _ := parser.ParseFile(fs, "main.go", src, 0)
ast.Print(fs, f)
Playgroundで動かす
package main
import "fmt"
func main() {
fmt.Println("Hello, 世界")
}
Hello, Worldの抽象構文木を手入力
21
Goの抽象構文木(AST)を手入力してHello, Worldを作る
http://qiita.com/tenntenn/items/0cbc6f1f00dc579fcd8c
Playgroundで動かす
*ast.File
[]ast.Decl
*ast.GenDecl *ast.FuncDecl
gopherを探せ
■ ソースコードからgopherを探そう
22
type Gopher struct { gopher string `json:"gopher"` }
func main() {
const gopher = "GOPHER"
gogopher := GOPHER()
gogopher.gopher = gopher
fmt.Println(gogopher)
}
func GOPHER() (gopher *Gopher) {
gopher = &Gopher{ gopher: "gopher" }
return
}
https://gist.github.com/tenntenn/ca92384795133b3ec5de0e0d7de5eec5
型チェックをしてみよう
■ 例:int型の変数を探す
23
cfg := &types.Config{Importer: importer.Default()}
info := &types.Info{
Defs: map[*ast.Ident]types.Object{},
}
cfg.Check("main", fs, []*ast.File{f}, info)
it := types.Universe.Lookup("int").Type()
for idnt, o := range info.Defs {
if o != nil &&
types.Identical(o.Type(), it) {
fmt.Println(fs.Position(idnt.Pos()), idnt)
}
}
https://gist.github.com/tenntenn/706d73e0d82105b0d25179578b953745
型チェックをしてみよう
■ 構造体型のgopherを探そう
24
type Gopher struct { gopher string `json:"gopher"` }
func main() {
const gopher = "GOPHER"
gogopher := GOPHER()
gogopher.gopher = gopher
fmt.Println(gogopher)
}
func GOPHER() (gopher *Gopher) {
gopher = &Gopher{ gopher: "gopher" }
return
}
Thank you!
twitter: @tenntenn
Qiita: tenntenn
connpass: tenntenn
25

Go静的解析ハンズオン