20220303のGoに関する記事は3件です。

Goの静的解析ツールblahbrah

Goの静的解析ツールを作ったので紹介したいと思います。 作ったもの blahbrah 何ができるのか タイトルにもある通り、Goの静的解析をしてくれます Brace( "{" や "}" )の前後にある空白行を検知することができます 次のコードを例に取ります。 突っ込み所はとてもありますが、一番気になる(?)のは、レビューで指摘されそうな、12行目や20行目に存在する空白行ではないでしょうか。 main.go 1 package main 2 3 import "fmt" 4 5 type cat struct { 6 name string 7 age int 8 } 9 10 func newCat(name string, age int) cat { 11 return cat{ 12 13 name: name, 14 age: age, 15 } 16 } 17 18 func foo(a, b int) int { 19 if a < b { 20 21 a = a + b 22 23 } 24 25 return 2 26 } 27 28 func main() { 29 30 fmt.Println("test comment") 31 32 } このコードに対して、 $ go vet -vettool=$GOPATH/bin/blahbrah $PKG_NAME を実行します。 すると、次のような出力が返ってきます。 $ go vet -vettool=$GOPATH/bin/blahbrah $PKG_NAME ./main.go:12:1: ineffectual blank line after the left brace ./main.go:20:1: ineffectual blank line after the left brace ./main.go:22:1: ineffectual blank line before the right brace ./main.go:29:1: ineffectual blank line after the left brace ./main.go:31:1: ineffectual blank line before the right brace 謎の空白がある行が指定されていますね! といったことをしてくれるツールを作成しました。 インストール方法 $ go install github.com/granddaifuku/blahbrah/cmd/blahbrah@latest 内部について 使ったツール skeleton:静的解析用の雛形を作ってくれるCLIツールです。 方針 前提として、gofmtやgoimportsなりでコードが整形された後に使用することを想定しています。 各ファイルを走査 ファイル内のコメントがある行を記録 ファイル内のBlock StatementとComposite Literalを発見したタイミングで下記チェックを行う チェックによって得られたレポートからDiagnosticを生成 といった流れで静的解析を行います。 チェック内容 A. 左Brace 左Braceに関して:Brace直後のコードがある行を探し、その行とBraceとの行間が1以上あってかつ、行間がコメントでなければ、無益な空行であるとみなしてレポートを作成します。 B. 右Brace 右Braceに関して:左Braceと同様の処理を直前のコードがある行に対して行います。 これらをソースコード例とともに示すと次のようになります。 ok.go 1 package main 2 3 import "fmt" 4 5 func main() { 6 // This is a successful comment 7 fmt.Println("test comment") 8 } 上記ok.go内では左Brace(5行目)と直後のコード(7行目)には1行以上の差がありますが、中間である6行目にはコメントがあるため、blahbrahはDiagnosticを生成しません。 ng.go 1 package main 2 3 import "fmt" 4 5 func main() { 6 7 fmt.Println("test comment") 8 } それに対して、上記ng.goでは6行目はただの空行となっているため、次のようにDiagnosticを生成します。 $ go vet -vettool=$GOPATH/bin/blahbrah $PKG_NAME ./main.go:6:1: ineffectual blank line after the left brace これらがblahbrahによる静的解析の大まかな流れです。 実装 blahbrah.go 1 func run(pass *analysis.Pass) (interface{}, error) { 2 for _, f := range pass.Files { 3 c := newChecker(pass.Fset, f.Decls, f.Comments) 4 reports := c.inspect() 5 for _, r := range reports { 6 pass.Reportf(token.Pos(r.pos), r.msg) 7 } 8 } 9 10 return nil, nil 11 } 上記のrun関数内2行目にて、各ファイルを走査しています。 3~4行目ではcheckerと呼ばれる構造体を生成し、その中でレポート(静的解析によって検知した位置やその内容)を作成、返却しています。 最後に、5~7行目にてDiagnosticを生成しています。 では、続いてchecker構造体について見ていきましょう。 checker.go 1 type checker struct { 2 fset *token.FileSet 3 decls []ast.Decl 4 comments map[int]struct{} 5 } 6 7 func newChecker( 8 fset *token.FileSet, 9 decls []ast.Decl, 10 cg []*ast.CommentGroup, 11 ) *checker { 12 // create a map whose key is the line number of comments 13 comments := make(map[int]struct{}) 14 15 for _, c := range cg { 16 if c.Text() == testAfterLbrace || c.Text() == testBeforeRbrace { 17 continue 18 } 19 start := fset.Position(c.Pos()).Line 20 end := fset.Position(c.End()).Line 21 for l := start; l < end+1; l++ { 22 comments[l] = struct{}{} 23 } 24 } 25 26 return &checker{ 27 fset: fset, 28 decls: decls, 29 comments: comments, 30 } 31 } checker構造体はフィールドにソースコードの位置に関する情報をもつfset、パッケージ内での宣言群であるdecls、コメントのある行を把握するためのcommentsを持っています。 7~31行目にかけて、newChecker()関数ではcheckerを生成します。 15~24行目ではコメント情報を受け取り、コメントがある行数をmapへと保存しています。 続いて、レポートを作成するinspect()メソッドの紹介です。 checker.go 1 func (c *checker) inspect() []report { 2 reports := make([]report, 0) 3 4 for _, d := range c.decls { 5 switch d := d.(type) { 6 case *ast.FuncDecl: 7 b := d.Body 8 ast.Inspect(b, func(n ast.Node) bool { 9 switch n := n.(type) { 10 case *ast.BlockStmt: 11 r := c.blockStmt(n) 12 if r != nil { 13 reports = append(reports, r...) 14 } 15 case *ast.CompositeLit: 16 r := c.compositeLit(n) 17 if r != nil { 18 reports = append(reports, r...) 19 } 20 } 21 22 return true 23 }) 24 } 25 } 26 27 return reports 28 } inspect()メソッドでは各宣言に対して、DFSによって生成されたASTを辿る、ast.inspect()メソッドを呼び出しています。 その中で、Block StatementとComposite Literalを発見した際に上記、チェック内容で説明した処理を行うメソッドを呼び出しています。 Block Statementのパターンを例に取り、実装を見ていきます。 checker.go 1 func (c *checker) blockStmt( 2 block *ast.BlockStmt, 3 ) []report { 4 lbraceLine := c.line(block.Lbrace) 5 rbraceLine := c.line(block.Rbrace) 6 7 if len(block.List) == 0 { 8 if rbraceLine-lbraceLine > 1 && !c.isComment(lbraceLine+1) { 9 return []report{ 10 { 11 pos: int(block.Rbrace) - c.col(block.Rbrace), 12 msg: beforeRbrace, 13 }, 14 } 15 } 16 17 return nil 18 } 19 20 reports := make([]report, 0) 21 22 firstLine := c.line(block.List[0].Pos()) 23 firstCol := c.col(block.List[0].Pos()) 24 if firstLine-lbraceLine > 1 && !c.isComment(lbraceLine+1) { 25 r := report{ 26 pos: int(block.List[0].Pos()) - firstCol, 27 msg: afterLbrace, 28 } 29 reports = append(reports, r) 30 } 31 32 endLine := c.line(block.List[len(block.List)-1].End()) 33 if rbraceLine-endLine > 1 && !c.isComment(rbraceLine-1) { 34 r := report{ 35 pos: int(block.Rbrace) - c.col(block.Rbrace), 36 msg: beforeRbrace, 37 } 38 reports = append(reports, r) 39 } 40 41 return reports 42 } blockStmt()メソッドはレポートのスライスを返します。 7~18行目では、左右Brace間にソースコードがなかった場合を考えています。 この時、Braceの行間が1以上かつ、その行がコメントではない場合、レポートを生成します。 22~30行目では左Braceに対してチェック内容Aを実施しています。 同様に32~39行目では右Braceに対してチェック内容Bを実施しています。 終わり 以上、Goの静的解析ツールblahbrahの紹介でした。 使っていただけたら嬉しいです。 また、改善や提案などもありがたく受け付けております。 参考 プログラミング言語Go完全入門 14. 静的解析とコード生成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

0-1ナップサック アルゴリズム

何が書いてあるか。 0-1ナップサック問題1のアルゴリズムのGo言語実装サンプル package main import ( "fmt" "math" ) type Kingyo struct { weight int kati int } func main(){ var N,x int fmt.Scanf("%d %d", &N,&x) kingyo:=make([]Kingyo,N) for i:=0;i<N;i++ { var w,k int fmt.Scanf("%d %d", &w,&k) kingyo[i]=Kingyo{w,k} } //fmt.Println(kingyo) choice:=make([][]int, N+1) for i:=0;i<N+1;i++ { choice[i]=make([]int, x+1) } for i:=1;i<=N;i++ { for j:=0;j<=x;j++ { choice[i][j]=choice[i-1][j] if j>kingyo[i-1].weight { choice[i][j]=int( math.Max( float64(choice[i][j]), float64(choice[i-1][j-kingyo[i-1].weight]+kingyo[i-1].kati)) ) } } } //dump(choice) fmt.Println(choice[N][x]) } func dump(choice [][]int) { for i:=0;i<len(choice);i++ { fmt.Println(choice[i]) } } 重量を越えないような物の組み合わせのうち、価値が最大になるようにな組み合わせを求める問題。 ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Go の API をフロントサイドから呼び出す

API 側に http.Handle("/", http.FileServer(http.Dir("static"))) "static" フォルダに html などを入れる。 詳しく main.go に http.Handle("/", http.FileServer(http.Dir("static"))) log.Fatal(http.ListenAndServe(":8080", nil)) と書いた場合。(main.go があるフォルダ)/static に index.html を入れる。 go run main.go などで実行すると、localhost:8080/ で index.html が表示される。 localhost:8080/test.html とかで static/test.html が表示される。 "static" フォルダは別フォルダにしても動く。"../test" とかでも問題ない。 "/" を "/top" にして localhost:8080/top にアクセスすると not found になったので、"/" は変えられなさそう。 題名と書いてること違うじゃないのか? はい、そう思います。 ただ、自分は上記のことがしたかったものの、フロントサイドを別 port で立てて cors を設定し、、、などをやっていて、かなり詰まりました。そういう人の検索に引っかかりやすい題名にしたつもりです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む