// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // +build ignore // The gendebug program takes gl.go and generates a version of it // where each function includes tracing code that writes its arguments // to the standard log. package main import ( "bytes" "flag" "fmt" "go/ast" "go/format" "go/parser" "go/printer" "go/token" "io/ioutil" "log" "os" "strconv" ) var outfile = flag.String("o", "", "result will be written to the file instead of stdout.") var fset = new(token.FileSet) func typeString(t ast.Expr) string { buf := new(bytes.Buffer) printer.Fprint(buf, fset, t) return buf.String() } func typePrinter(t string) string { switch t { case "[]float32", "[]byte": return "len(%d)" } return "%v" } func typePrinterArg(t, name string) string { switch t { case "[]float32", "[]byte": return "len(" + name + ")" } return name } func die(err error) { fmt.Fprintf(os.Stderr, err.Error()) os.Exit(1) } func main() { flag.Parse() f, err := parser.ParseFile(fset, "consts.go", nil, parser.ParseComments) if err != nil { die(err) } entries := enum(f) f, err = parser.ParseFile(fset, "gl.go", nil, parser.ParseComments) if err != nil { die(err) } buf := new(bytes.Buffer) fmt.Fprint(buf, preamble) fmt.Fprintf(buf, "func (v Enum) String() string {\n") fmt.Fprintf(buf, "\tswitch v {\n") for _, e := range dedup(entries) { fmt.Fprintf(buf, "\tcase 0x%x: return %q\n", e.value, e.name) } fmt.Fprintf(buf, "\t%s\n", `default: return fmt.Sprintf("gl.Enum(0x%x)", uint32(v))`) fmt.Fprintf(buf, "\t}\n") fmt.Fprintf(buf, "}\n\n") for _, d := range f.Decls { // Before: // func StencilMask(mask uint32) { // C.glStencilMask(C.GLuint(mask)) // } // // After: // func StencilMask(mask uint32) { // defer func() { // errstr := errDrain() // log.Printf("gl.StencilMask(%v) %v", mask, errstr) // }() // C.glStencilMask(C.GLuint(mask)) // } fn, ok := d.(*ast.FuncDecl) if !ok { continue } if fn.Recv != nil { continue } var ( params []string paramTypes []string results []string resultTypes []string ) // Print function signature. fmt.Fprintf(buf, "func %s(", fn.Name.Name) for i, p := range fn.Type.Params.List { if i > 0 { fmt.Fprint(buf, ", ") } ty := typeString(p.Type) for i, n := range p.Names { if i > 0 { fmt.Fprint(buf, ", ") } fmt.Fprintf(buf, "%s ", n.Name) params = append(params, n.Name) paramTypes = append(paramTypes, ty) } fmt.Fprint(buf, ty) } fmt.Fprintf(buf, ") (") if fn.Type.Results != nil { for i, r := range fn.Type.Results.List { if i > 0 { fmt.Fprint(buf, ", ") } ty := typeString(r.Type) if len(r.Names) == 0 { name := fmt.Sprintf("r%d", i) fmt.Fprintf(buf, "%s ", name) results = append(results, name) resultTypes = append(resultTypes, ty) } for i, n := range r.Names { if i > 0 { fmt.Fprint(buf, ", ") } fmt.Fprintf(buf, "%s ", n.Name) results = append(results, n.Name) resultTypes = append(resultTypes, ty) } fmt.Fprint(buf, ty) } } fmt.Fprintf(buf, ") {\n") // gl.GetError is used by errDrain, which will be made part of // all functions. So do not apply it to gl.GetError to avoid // infinite recursion. skip := fn.Name.Name == "GetError" if !skip { // Insert a defer block for tracing. fmt.Fprintf(buf, "defer func() {\n") fmt.Fprintf(buf, "\terrstr := errDrain()\n") switch fn.Name.Name { case "GetUniformLocation", "GetAttribLocation": fmt.Fprintf(buf, "\tr0.name = name\n") } fmt.Fprintf(buf, "\tlog.Printf(\"gl.%s(", fn.Name.Name) for i, p := range paramTypes { if i > 0 { fmt.Fprint(buf, ", ") } fmt.Fprint(buf, typePrinter(p)) } fmt.Fprintf(buf, ") ") if len(resultTypes) > 1 { fmt.Fprint(buf, "(") } for i, r := range resultTypes { if i > 0 { fmt.Fprint(buf, ", ") } fmt.Fprint(buf, typePrinter(r)) } if len(resultTypes) > 1 { fmt.Fprint(buf, ") ") } fmt.Fprintf(buf, "%%v\"") for i, p := range paramTypes { fmt.Fprintf(buf, ", %s", typePrinterArg(p, params[i])) } for i, r := range resultTypes { fmt.Fprintf(buf, ", %s", typePrinterArg(r, results[i])) } fmt.Fprintf(buf, ", errstr)\n") fmt.Fprintf(buf, "}()\n") } // Print original body of function. for _, s := range fn.Body.List { printer.Fprint(buf, fset, s) fmt.Fprintf(buf, "\n") } fmt.Fprintf(buf, "}\n\n") } b, err := format.Source(buf.Bytes()) if err != nil { os.Stdout.Write(buf.Bytes()) die(err) } if *outfile == "" { os.Stdout.Write(b) return } if err := ioutil.WriteFile(*outfile, b, 0666); err != nil { die(err) } } const preamble = `// Copyright 2014 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Generated from gl.go using go generate. DO NOT EDIT. // See doc.go for details. // +build linux darwin // +build gldebug package gl // #include "work.h" import "C" import ( "fmt" "log" "math" "unsafe" ) func errDrain() string { var errs []Enum for { e := GetError() if e == 0 { break } errs = append(errs, e) } if len(errs) > 0 { return fmt.Sprintf(" error: %v", errs) } return "" } ` type entry struct { name string value int } // enum builds a list of all GL constants that make up the gl.Enum type. func enum(f *ast.File) []entry { var entries []entry for _, d := range f.Decls { gendecl, ok := d.(*ast.GenDecl) if !ok { continue } if gendecl.Tok != token.CONST { continue } for _, s := range gendecl.Specs { v, ok := s.(*ast.ValueSpec) if !ok { continue } if len(v.Names) != 1 || len(v.Values) != 1 { continue } val, err := strconv.ParseInt(v.Values[0].(*ast.BasicLit).Value, 0, 32) if err != nil { log.Fatalf("enum %s: %v", v.Names[0].Name, err) } entries = append(entries, entry{v.Names[0].Name, int(val)}) } } return entries } func dedup(entries []entry) []entry { // Find all duplicates. Use "%d" as the name of any value with duplicates. seen := make(map[int]int) for _, e := range entries { seen[e.value]++ } var dedup []entry for _, e := range entries { switch seen[e.value] { case 0: // skip, already here case 1: dedup = append(dedup, e) default: // value is duplicated dedup = append(dedup, entry{fmt.Sprintf("%d", e.value), e.value}) seen[e.value] = 0 } } return dedup }